/**
	@file
	Input/output functions. Working with ports, screen, keyboard.
	@note Keyboard input not implemented yet.
	@author Konstantin Kastanov
*/
#include "headers/io.h"
#include "headers/colours.h"
#include "headers/dataTypes.h"
#include "headers/string.h"
#include "../multitasking/headers/task.h"
#include "../adt/headers/queue.h"


#define VIDEO_MEM_ADDRESS 0xB8000
#define BUFF_LNGT 10
#define BG_COLOUR CYAN
#define TEXT_COLOUR WHITE

static void moveCursor();
static void scroll();
static void updateAttribute();
static virtual_console_type* currentConsole = NULL;
static virtual_console_type* focusedConsole = NULL;
static u8int cursorX = 0, cursorY = 0, textColour = TEXT_COLOUR, bgColour = BG_COLOUR, xBlocked = 0, yBlocked = 0;
static u8int attributeByte = (BG_COLOUR << 4) | (TEXT_COLOUR& 0x0F); //Current attribute byte
static u16int *videoMemory = (u16int*)VIDEO_MEM_ADDRESS;
void makeVirtualConsoleFocused(task_t* task);
task_t* focused_task = 0;
extern task_t* current_task;
extern volatile BOOL critical_section;

void setCurrentVirtualConsole(virtual_console_type* console)
{
	currentConsole = console;
}

virtual_console_type* getCurrentVirtualConsole()
{
	return currentConsole;
}

void cloneRealConsole(virtual_console_type* console)
{
	critical_section = true;
	console->attribute_byte = attributeByte;
	console->text_colour = textColour;
	console->bg_colour = bgColour;
	console->x_cursor = cursorX;
	console->y_cursor = cursorY;
	console->x_blocked = xBlocked;
	console->y_blocked = yBlocked;
	memcpy((u8int*)console->buffer, (u8int*)videoMemory, 4000);
	critical_section = false;
}

void makeVirtualConsoleFocused(task_t* task)
{
	critical_section = true;
	
	virtual_console_type* console;
	console = task->console;
	memcpy((u8int*)videoMemory, (u8int*)console->buffer, 4000);
	attributeByte = console->attribute_byte;
	textColour = console->text_colour;
	bgColour = console->bg_colour;
	cursorX = console->x_cursor;
	cursorY = console->y_cursor;
	moveCursor();
	if(focusedConsole)
		focusedConsole->focused = false;
	console->focused = true;
	focusedConsole = console;
	focused_task = task;
	
	critical_section = false;
}

void clearVirtualConsole(virtual_console_type* console)
{
	critical_section = true;
	
	virtual_console_type* tmp;
	console->attribute_byte = (BG_COLOUR << 4) | (TEXT_COLOUR& 0x0F);
	console->text_colour = TEXT_COLOUR;
	console->bg_colour = BG_COLOUR;
	console->x_cursor = 0;
	console->y_cursor = 0;
	console->x_blocked = 0;
	console->y_blocked = 0;
	tmp = currentConsole;
	setCurrentVirtualConsole(console);
	clearScreen();
	setCurrentVirtualConsole(tmp);
	
	critical_section = false;
}

void block_cursor()
{
	if(currentConsole)
	{
		currentConsole->x_blocked = currentConsole->x_cursor;
		currentConsole->y_blocked = currentConsole->y_cursor;
	}
	if(!currentConsole || currentConsole->focused)
	{
		xBlocked = cursorX;
		yBlocked = cursorY;
	}
}

void release_cursor()
{
	if(currentConsole)
	{
		currentConsole->x_blocked = 0;
		currentConsole->y_blocked = 0;
	}
	if(!currentConsole || currentConsole->focused)
	{
		xBlocked = 0;
		yBlocked = 0;
	}
}

/**
	Puts character of a specified colour (text and background) to a screen (to a specified 
	location).
	@param ascii ASCII code of a character
	@param bgColour Background colour
	@param fgColour Foreground colour
	@param x X location coordinate
	@param y Y location coordinate
*/
void putColouredChar(u8int ascii, u8int bgColour, u8int fgColour, u8int x, u8int y)
{
	critical_section = true;
  u8int attribute = (bgColour << 4) | (fgColour & 0x0F);
  if(currentConsole)
  {
	  currentConsole->buffer[y*80 + x] = ascii | (attribute << 8);
  }
  if((!currentConsole) || (currentConsole->focused))
  {
	  videoMemory[y*80 + x] = ascii | (attribute << 8); //Forms and puts a corresponding word (low byte is ascii, high byte - attribute (coloura))
						//We don't need to multiply by 2, because we are refferencing 16bit array 
						//(will be done automatically)
  }
  critical_section = false;
}

/**
	Pust a null terminated ASCII string to a screen at current cursor location.
	Moves cursos. Relies on \ref putChar()
	@param message Pointer to null terminated ASCII string to display
*/
void putString(s8int *message)
{
	critical_section = true;
  while((*message) != 0)
  {
    putChar(*(message++));
  }
  critical_section = false;
}

/**
	Sends one byte to a specified port.
	@param port Destination port number
	@param value Data to send
*/
void outb(u16int port, u8int value)
{
    asm volatile ("outb %1, %0" : : "dN" (port), "a" (value));
}

/**
	Gets one byte from a specified port.
	@param port Source port number
	@return Byte recieved from the port
*/
u8int inb(u16int port)
{
   u8int ret;
   asm volatile("inb %1, %0" : "=a" (ret) : "dN" (port));
   return ret;
}

/**
	Gets two byte from a specified port.
	@param port Source port number
	@return Word (2 bytes) recieved from the port
*/
u16int inw(u16int port)
{
   u16int ret;
   asm volatile ("inw %1, %0" : "=a" (ret) : "dN" (port));
   return ret;
}

/**
	Moves cursor to location specified by cursorX and cursorY
	global variables.
*/
static void moveCursor()
{
	BOOL backup = critical_section;
	critical_section = true;
   u16int cursorLocation = cursorY * 80 + cursorX;
   outb(0x3D4, 14);                  // Tell the VGA board we are setting the high cursor byte.
   outb(0x3D5, cursorLocation >> 8); // Send the high cursor byte.
   outb(0x3D4, 15);                  // Tell the VGA board we are setting the low cursor byte.
   outb(0x3D5, cursorLocation);      // Send the low cursor byte.
   critical_section = backup;
}

/**
	Puts formatted string to a screen at current cursor location.
	Used similar to a standart C printf()
	@param str Pointer to a formatted null terminating string
	@param ... Arguments
	@warning Only those types are supported: \%d, \%h, \%H, \%c, \%s (yet).
			Precision change and flags are not supported.
	@todo Implement float.
	
*/
void printf(s8int *str, ...) //In progress - float not supported yet
{
	BOOL backup = critical_section;
	critical_section = true;
  s32int sArg = 0;
  u32int uArg = 0;
  u32int offset = 12; //EBP old value, return address, str variable (4 * 3 = 12)
  u32int i = 0;
  s8int buff[BUFF_LNGT];
  
  
  for(i = 0; str[i] != 0; i++)
  {
    if(str[i] == '%')
    {
      if(str[i+1] == 'd')
      {
	asm volatile("movl (%%ebp, %1), %0" : "=r" (sArg) : "r" (offset): "%esp");
	offset += 4;
	decimalToString(sArg, buff, BUFF_LNGT);
	putString(buff);
      }
      else if(str[i+1] == 'h')
      {
	asm volatile("movl (%%ebp, %1), %0" : "=r" (sArg) : "r" (offset): "%esp");
	offset += 4;
	hexToString(sArg, buff, BUFF_LNGT, false, false);
	putString(buff);
      }
      else if(str[i+1] == 'H')
      {
	asm volatile("movl (%%ebp, %1), %0" : "=r" (sArg) : "r" (offset): "%esp");
	offset += 4;
	hexToString(sArg, buff, BUFF_LNGT, true, false);
	putString(buff);
      }
      else if(str[i+1] == 'c')
      {
	asm volatile("movl (%%ebp, %1), %0" : "=r" (uArg) : "r" (offset): "%esp");
	offset += 4;
	putChar((u8int)uArg);
      }
      else if(str[i+1] == 's')
      {
	asm volatile("movl (%%ebp, %1), %0" : "=r" (uArg) : "r" (offset): "%esp");
	offset += 4;
	putString((s8int*)uArg);
      }
      else if(str[i+1] == '%')
      {
	putChar('%');
      }
      else if(str[i+1] != 0) //Else (any unsupported type)
      {
	putChar(str[i+1]);
      }
      
      if(str[i+1] != 0)
	i++;
    }
    else
      putChar(str[i]);
  }
	critical_section = backup;
}

/**
	Scrolls everything (text) up if cursor is lower than the end of the screen.
*/
static void scroll()
{
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		u16int blank = ' ' | (currentConsole->attribute_byte << 8);

		// Row 25 is the end, this means we need to scroll up
		if(currentConsole->y_cursor > 24)
		{
			// Move text up
			int i;
			for (i = 0; i < 24*80; i++)
			{
				currentConsole->buffer[i] = currentConsole->buffer[i + 80];
			}

			// The last line should now be blank. Do this by writing
			// 80 spaces to it.
			for (i = 24*80; i < 25*80; i++)
			{
				currentConsole->buffer[i] = blank;
			}
			// Move cursor to the last line
			currentConsole->y_cursor = 24;
		}
	}
	
	if((!currentConsole) || (currentConsole->focused))
	{
	
		u16int blank = ' ' | (attributeByte << 8);

		// Row 25 is the end, this means we need to scroll up
		if(cursorY > 24)
		{
			// Move text up
			int i;
			for (i = 0; i < 24*80; i++)
			{
				videoMemory[i] = videoMemory[i + 80];
			}

			// The last line should now be blank. Do this by writing
			// 80 spaces to it.
			for (i = 24*80; i < 25*80; i++)
			{
				videoMemory[i] = blank;
			}
			// Move cursor to the last line
			cursorY = 24;
		}
   }
  
	critical_section = backup;
}

/**
	Calculate attribute byte using bgColour and textColour
	and set \c attributeByte.
*/
static void updateAttribute() 
{
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		currentConsole->attribute_byte = (currentConsole->bg_colour << 4) | (currentConsole->text_colour & 0x0F);
	}
	if((!currentConsole) || (currentConsole->focused))
	{
		attributeByte = (bgColour << 4) | (textColour & 0x0F);
	}
	critical_section = backup;
}

/**
	Puts ASCII character to a screen at current cursor position.
	Moves cursor. Scrolls screen.
	Supported escape sequences:
	\\n, \\b, \\r, \\t.
	@param c ASCII code of a character to display
*/
void putChar(char c)
{
   //u16int *location;
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		if (c == '\b' && (currentConsole->x_cursor || currentConsole->y_cursor))
		{
// 			if(!(currentConsole->x_blocked || currentConsole->y_blocked) ||
// 				!((currentConsole->x_blocked || currentConsole->y_blocked) && 
// 					(currentConsole->y_cursor == currentConsole->y_blocked) &&
// 					(currentConsole->x_cursor <= currentConsole->x_blocked)))
// 			{
// 				
				if(currentConsole->x_cursor)
				{
					currentConsole->x_cursor--;
				}
				else
				{
					currentConsole->x_cursor = 79;
					currentConsole->y_cursor--;
				}
				currentConsole->buffer[currentConsole->y_cursor*80 + currentConsole->x_cursor] = ' ' | (currentConsole->attribute_byte << 8);
// 			}
		}

		// Handle a tab by increasing the cursor's X, but only to a point
		// where it is divisible by 8.(set last three bits to zero)
		else if (c == '\t')
		{
			currentConsole->x_cursor = (currentConsole->x_cursor+8) & ~(0b111);
		}

		// Handle carriage return
		else if (c == '\r')
		{
			currentConsole->x_cursor = 0;
		}

		// Handle newline by moving cursor back to left and increasing the row
		else if (c == '\n')
		{
			currentConsole->x_cursor = 0;
			currentConsole->y_cursor++;
		}
		// Handle any other printable character.
		else if(c >= ' ')
		{
			currentConsole->buffer[currentConsole->y_cursor*80 + currentConsole->x_cursor] = c | (currentConsole->attribute_byte << 8);
			currentConsole->x_cursor++;
		}

		// Check if we need to insert a new line because we have reached the end
		// of the screen.
		if (currentConsole->x_cursor >= 80)
		{
			currentConsole->x_cursor = 0;
			currentConsole->y_cursor ++;
		}
		scroll();
	}
	if((!currentConsole) || (currentConsole->focused))
	{
	// Handle a backspace, by moving the cursor back one space
		if (c == '\b' && (cursorX || cursorY))
		{
			if(cursorX)
			{
				cursorX--;
			}
			else
			{
				cursorX = 79;
				cursorY--;
			}
			videoMemory[cursorY*80 + cursorX] = ' ' | (attributeByte << 8);
		}

		// Handle a tab by increasing the cursor's X, but only to a point
		// where it is divisible by 8.(set last three bits to zero)
		else if (c == '\t')
		{
			cursorX = (cursorX+8) & ~(0b111);
		}

		// Handle carriage return
		else if (c == '\r')
		{
			cursorX = 0;
		}

		// Handle newline by moving cursor back to left and increasing the row
		else if (c == '\n')
		{
			cursorX = 0;
			cursorY++;
		}
		// Handle any other printable character.
		else if(c >= ' ')
		{
			videoMemory[cursorY*80 + cursorX] = c | (attributeByte << 8);
			cursorX++;
		}

		// Check if we need to insert a new line because we have reached the end
		// of the screen.
		if (cursorX >= 80)
		{
			cursorX = 0;
			cursorY ++;
		}
		scroll();
		moveCursor();
	}  
	critical_section = backup;
}

/**
	Converts signed int to a string in decimal format.
	String is truncated if result does not fit
	(number of required characters > \c buffLength)
	@param num Number to convert
	@param[out] buff Buffer to put result string
	@param buffLength Length of the buffer (max string size)
	@return Pointer to \c buff (just for ease of use (c sugar etc.))
*/
s8int* decimalToString(s32int num, s8int *buff, u32int buffLength)
{
  u32int digitsN = 0, i = 0;
  u8int temp = 0;
  u8int neg = false;
  if (num == 0)
  {
    buff[0] = '0';
    buff[1] = 0;
	return buff;
  }
  if(num < 0)
  {
    neg = true;
    num *= -1;
    buff[0] = '-';
    buff++;
    buffLength--; //Reserve one byte for minus sign
  }
    
  while((num > 0) && (digitsN < buffLength))
  {
    buff[digitsN++] = '0' + (num % 10);
    num /= 10;
  }
  

  
  for(i = 0; i < (u32int)(digitsN/2); i++) //Put in reverse order
  {
    temp = buff[i];
    buff[i] = buff[digitsN - 1 - i];
    buff[digitsN - 1 -i] = temp;
  }
  buff[digitsN] = 0; //Null terminating string
  return buff - neg;
}

/**
	Converts signed int to a string in hexademical format.
	String is truncated if result does not fit
	(number of required characters > \c buffLength)
	@param num Number to convert
	@param[out] buff Buffer to put result string
	@param buffLength Length of the buffer (max string size)
	@param uppercase If set to 0 (false), lowercase characters will
			be used. In onther case - uppercase characters are used.
	@return Pointer to \c buff (just for ease of use (c sugar etc.))
*/
s8int* hexToString(s32int num, s8int *buff, u32int buffLength, BOOL uppercase, BOOL sgned)
{
  u32int digitsN = 0, i = 0;
  u32int unsNum = 0;
  u8int temp = 0;
  BOOL neg = false;
  if (num == 0)
  {
    buff[0] = '0';
    buff[1] = 0;
	return buff;
  }
  if((num < 0) && sgned)
  {
    neg = true;
    num *= -1;
    buff[0] = '-';
    buff++;
    buffLength--; //Reserve one byte for minus sign
  }
  unsNum = num;
    //printf("Num: %d\n");
  while((unsNum != 0) && (digitsN < buffLength))
  {
    temp = unsNum % 16;
	//printf("temp: %d\n", temp);
    if(temp >= 10)
      temp = temp - 10 + (uppercase?'A':'a');
    else
        temp += '0';
    buff[digitsN++] = temp;
    unsNum /= 16;
  }
  
  
  for(i = 0; i < (u32int)(digitsN/2); i++) //Put in reverse order
  {
    temp = buff[i];
    buff[i] = buff[digitsN - 1 - i];
    buff[digitsN - 1 -i] = temp;
  }
  buff[digitsN] = 0;
  return buff - neg;
}


/**
	Clears the entire screen.
	@note Actually fills entire screen with spaces
*/
void clearScreen()
{
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		u16int blank = 0x20 /* space */ | (currentConsole->attribute_byte << 8);

		int i;
		for (i = 0; i < 80*25; i++)
		{
			currentConsole->buffer[i] = blank;
		}

		// Move the hardware cursor back to the start.
		currentConsole->x_cursor = 0;
		currentConsole->y_cursor = 0;
	}
	if((!currentConsole) || (currentConsole->focused))
	{
		u16int blank = 0x20 /* space */ | (attributeByte << 8);

		int i;
		for (i = 0; i < 80*25; i++)
		{
			videoMemory[i] = blank;
		}

		// Move the hardware cursor back to the start.
		cursorX = 0;
		cursorY = 0;
		moveCursor();
	}
	critical_section = backup;
}

/**
	Moves a cursor to a specified location.
	@param x X location coordinate
	@param y Y location coordinate
*/
void setCursor(u8int x, u8int y)
{
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		currentConsole->x_cursor = x;
		currentConsole->x_cursor = y;
	}
	if((!currentConsole) || (currentConsole->focused))
	{
		cursorX = x;
		cursorY = y;
		moveCursor();
	}
	critical_section = backup;
}

/**
	Sets colour of text. Does not affect already printed text
	colour. Colours are defined in \ref colours.h.
	@param colour New text colour 
*/
void setTextColour(u8int colour)
{
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		currentConsole->text_colour = colour;
	}
	if((!currentConsole) || (currentConsole->focused))
	{
		textColour = colour;
	}
	updateAttribute();
	critical_section = backup;
}

/**
	Sets colour of background. Does not affect already 
	printed text background colour. Colours are defined 
	in \ref colours.h.
	@param colour New background colour 
*/
void setBgColour(u8int colour)
{
	BOOL backup = critical_section;
	critical_section = true;
	if(currentConsole)
	{
		currentConsole->bg_colour = colour;
	}
	if((!currentConsole) || (currentConsole->focused))
	{
		bgColour = colour;
	}
	updateAttribute();
	critical_section = backup;
}

u8int* getString(u8int* buff)
{

	u8int n = 0, last_char = 0;
	clear_queue(current_task->input_stream);
	
		while(last_char != '\n')
		{
			block_process(current_task, RES_QUEUE, current_task->input_stream);
			while(!queue_is_empty(current_task->input_stream))
			{
				last_char = dequeue(current_task->input_stream);
				if(last_char && last_char < 128)
				{
					if(last_char == '\b')
					{
						if(n)
						{
							n--;
							putChar((s8int)last_char);
						}
					}
					else
					{
						buff[n++] = last_char;
						putChar((s8int)last_char);
					}
					
				}
			}
		}
		buff[n-1]=0;
		return buff;
}
